﻿using UnityEngine;
using System.Collections.Generic;

namespace TinyAsset
{
    public class AssetSystem
    {
        Manifest CurrentManifest;

        int m_Stack = 0;
        CacheRegistry<AssetLoader> aCacheRegistry = new CacheRegistry<AssetLoader>();
        CacheRegistry<AssetBundleLoader> bCacheRegistry = new CacheRegistry<AssetBundleLoader>();

        public AssetSystem() { }

        public void Update(float deltaTime, float unscaledDeltaTime)
        {
            aCacheRegistry.Update(deltaTime, unscaledDeltaTime);
            bCacheRegistry.Update(deltaTime, unscaledDeltaTime);
        }

        public void SetCurrentManifest(Manifest manifest)
        {
            CurrentManifest = manifest;
        }

        public AssetOperationHandle LoadAsset(string path, System.Type assetType)
        {
            if (Locate(path, out string assetPath, out string bundleName, out string assetName))
            {
                AssetBundleLoader loader = null;
                if (bundleName != null)
                    loader = LoadAssetBundleInternal(bundleName, false);
                return LoadAssetInternal(assetPath, assetName, assetType, false, loader).CreateAssetOperationHandle();
            }
            return null;
        }

        public AssetOperationHandle LoadAssetAsync(string path, System.Type assetType)
        {
            if (Locate(path, out string assetPath, out string bundleName, out string assetName))
            {
                AssetBundleLoader loader = null;
                if (bundleName != null)
                    loader = LoadAssetBundleInternal(bundleName, true);
                return LoadAssetInternal(assetPath, assetName, assetType, true, loader).CreateAssetOperationHandle();
            }
            return null;
        }

        public AssetOperationHandle LoadScene(string path, bool additive)
        {
            if (Locate(path, out string assetPath, out string bundleName, out string assetName))
            {
                AssetBundleLoader loader = null;
                if (bundleName != null)
                    loader = LoadAssetBundleInternal(bundleName, false);
                return LoadSceneInternal(assetPath, assetName, additive, false, loader).CreateAssetOperationHandle();
            }
            return null;
        }

        public AssetOperationHandle LoadSceneAsync(string path, bool additive)
        {
            if (Locate(path, out string assetPath, out string bundleName, out string assetName))
            {
                AssetBundleLoader loader = null;
                if (bundleName != null)
                    loader = LoadAssetBundleInternal(bundleName, true);
                return LoadSceneInternal(assetPath, assetName, additive, true, loader).CreateAssetOperationHandle();
            }
            return null;
        }

        public void UnloadAsset(string key)
        {
            UnloadAssetInternal(key);
        }

        AssetLoader LoadAssetInternal(string assetPath, string assetName, System.Type assetType, bool async, AssetBundleLoader loader)
        {
            AssetLoader assetLoader = null;
            if (!aCacheRegistry.TryGetValue(assetPath, out assetLoader))
            {
                if (AssetBundleUtility.kUseAssetDatabase)
                    assetLoader = new AssetDatabaseLoader(assetPath, assetType);
                else
                    assetLoader = new BundledAssetLoader(assetPath, assetName, assetType, loader);

                aCacheRegistry.MakeCache(assetLoader);
            }

            if (async)
            {
                aCacheRegistry.AddLast(assetLoader);
                assetLoader.LoadAsync();
            }
            else
                assetLoader.Load();

            return assetLoader;
        }

        AssetLoader LoadSceneInternal(string assetPath, string assetName, bool additive, bool async, AssetBundleLoader loader)
        {
            AssetLoader assetLoader = null;
            if (!aCacheRegistry.TryGetValue(assetPath, out assetLoader))
            {
                if (AssetBundleUtility.kUseAssetDatabase)
                    assetLoader = new AssetDatabaseLoader(assetPath, additive, m_Stack++);
                else
                    assetLoader = new BundledAssetLoader(assetPath, assetName, additive, m_Stack++, loader);

                aCacheRegistry.MakeCache(assetLoader);
            }

            if (async)
            {
                aCacheRegistry.AddLast(assetLoader);
                assetLoader.LoadAsync();
            }
            else
                assetLoader.Load();

            return assetLoader;
        }

        AssetBundleLoader LoadAssetBundleInternal(string bundleName, bool async)
        {
            AssetBundleLoader loader;
            if (!bCacheRegistry.TryGetValue(bundleName, out loader))
            {
                List<AssetBundleLoader> dependencies = new List<AssetBundleLoader>();
                var entity = CurrentManifest.GetAssetBundleEntity(bundleName);
                foreach (var dependency in entity.dependencies)
                    dependencies.Add(LoadAssetBundleInternal(CurrentManifest.assetBundleEntities[dependency].name, async));
                loader = new AssetBundleLoader(bundleName, dependencies.ToArray());
                bCacheRegistry.MakeCache(loader);
            }
            else
            {
                var entity = CurrentManifest.GetAssetBundleEntity(bundleName);
                foreach (var dependency in entity.dependencies)
                    LoadAssetBundleInternal(CurrentManifest.assetBundleEntities[dependency].name, async);
            }

            if (async)
            {
                bCacheRegistry.AddLast(loader);
                loader.LoadAsync();
            }
            else
                loader.Load();

            return loader;
        }

        void UnloadAssetInternal(string assetPath)
        {
            if (aCacheRegistry.TryGetValue(assetPath, out AssetLoader loader))
            {
                aCacheRegistry.Unload(assetPath);
                if (loader is BundledAssetLoader bundledAssetLoader)
                    UnloadAssetBundleInternal(bundledAssetLoader.GetAssetBundleName());
            }
        }

        void UnloadAssetBundleInternal(string bundleName)
        {
            if (bCacheRegistry.TryGetValue(bundleName, out AssetBundleLoader loader))
            {
                bCacheRegistry.Unload(bundleName);
                var entity = CurrentManifest.GetAssetBundleEntity(bundleName);
                foreach (var dependency in entity.dependencies)
                    UnloadAssetBundleInternal(CurrentManifest.assetBundleEntities[dependency].name);
            }
        }

        bool Locate(string name, out string assetPath, out string bundleName, out string assetName)
        {
            assetName = null;
            assetPath = null;
            bundleName = null;

            foreach (var item in CurrentManifest.bundledAssetKeys)
            {
                assetPath = CurrentManifest.dirs[item.dir] + item.name;
                if (assetPath.StartsWith(name))
                {
                    if (item.bundle != -1)
                    {
                        bundleName = CurrentManifest.assetBundleEntities[item.bundle].name;
                        assetName = item.name;
                    }
                    return true;
                }
            }
            return false;
        }
    }
}